Skip to content

fix: skip StaticCallToMethodCallRector when parent declares final __construct#8001

Merged
TomasVotruba merged 2 commits into
mainfrom
claude/rector-latest-issue-mhVvr
May 21, 2026
Merged

fix: skip StaticCallToMethodCallRector when parent declares final __construct#8001
TomasVotruba merged 2 commits into
mainfrom
claude/rector-latest-issue-mhVvr

Conversation

@TomasVotruba
Copy link
Copy Markdown
Member

@TomasVotruba TomasVotruba commented May 21, 2026

Summary

Fixes #9766 (reported in rectorphp/rector).

StaticCallToMethodCallRector triggered a PHP fatal error when the parent class declared __construct as final:

Cannot override final method ParentClass::__construct()

The rule was cloning the parent's final __construct and inserting it into the child class to perform constructor injection — but PHP forbids any __construct override when the parent's is final (unlike private, where a fresh child constructor is still valid PHP).

Changes

  • ClassDependencyManipulator — add hasFinalParentConstructor(Class_): walks the ancestor chain and returns true when the nearest constructor is final and the class has no own constructor.
  • FuncCallStaticCallToMethodCallAnalyzer::matchTypeProvidingExpr() — widen return type to … | null; return null when hasFinalParentConstructor() is true so the caller can skip the transformation.
  • StaticCallToMethodCallRector and FuncCallToMethodCallRector — handle null return by leaving the node unchanged (no $hasChanged = true).
  • New test fixture skip_when_parent_has_final_construct.php.inc (no ----- = expects no change) + source class ResourceWithFinalConstruct with final public function __construct.

Test plan

  • vendor/bin/phpunit rules-tests/Transform/Rector/StaticCall/StaticCallToMethodCallRector/StaticCallToMethodCallRectorTest.php — new fixture asserts code is left unchanged; existing fixtures (public parent, private parent, no parent) still pass.
  • vendor/bin/phpunit rules-tests/Transform/Rector/FuncCall/FuncCallToMethodCallRector/ — no regressions in the sibling rector.

claude and others added 2 commits May 21, 2026 17:54
…onstruct

When the nearest ancestor constructor is `final`, PHP forbids declaring
any `__construct` in the child class. Previously the rector cloned the
parent's `final __construct` and inserted it into the child, producing
"Cannot override final method ParentClass::__construct()".

Add `ClassDependencyManipulator::hasFinalParentConstructor()` to detect
this condition. `FuncCallStaticCallToMethodCallAnalyzer::matchTypeProvidingExpr()`
now returns `null` (and its return type is widened to include `null`)
when constructor injection is blocked by a final parent constructor.
Both `StaticCallToMethodCallRector` and `FuncCallToMethodCallRector`
skip the transformation when `null` is returned.

Fixes #9766

https://claude.ai/code/session_019aUU1dheeuij6J55RBaYdZ
@TomasVotruba TomasVotruba merged commit 8a22661 into main May 21, 2026
78 checks passed
@TomasVotruba TomasVotruba deleted the claude/rector-latest-issue-mhVvr branch May 21, 2026 18:22
@TomasVotruba
Copy link
Copy Markdown
Member Author

LGTM 👌

continue;
}

$parentClass = $this->astResolver->resolveClassFromClassReflection($ancestor);
Copy link
Copy Markdown
Member

@samsonasik samsonasik May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be not needed, there is $ancestor->getNativeMethod(MethodName::CONSTRUCT)->isFinalByKeyword()->yes() for it, see

https://github.com/phpstan/phpstan-src/blob/c853aa27f93ac6c73146107f709712cf6034c220/src/Reflection/Native/NativeMethodReflection.php#L158

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants